/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <gtest/gtest.h>
#include <gtest/hwext/gtest-multithread.h>

#include <string>

#include "common.h"
#include "rdb_errno.h"
#include "rdb_helper.h"
#include "rdb_open_callback.h"
#include "shared_block.h"
#include "sqlite_shared_result_set.h"
#include "value_object.h"

using namespace testing::ext;
using namespace testing::mt;
using namespace OHOS::NativeRdb;
using Asset = ValueObject::Asset;
using Assets = ValueObject::Assets;
class RdbMultiThreadConnectionTest : public testing::Test {
public:
    static void SetUpTestCase(void);
    static void TearDownTestCase(void);
    void SetUp();
    void TearDown();
    void GenerateDefaultTable();
    void GenerateAssetsTable();
    void GenerateTimeoutTable();
    void CheckResultSetAttribute(std::shared_ptr<ResultSet> rstSet, int pos, bool isStart, bool isAtFirstRow,
                                 bool isEnded);
    void CreateTable();
    void Query();

    static const std::string DATABASE_NAME;
    static std::shared_ptr<RdbStore> store;
    static const std::string CREATE_TABLE_SQL;
};

const std::string RdbMultiThreadConnectionTest::DATABASE_NAME = RDB_TEST_PATH + "connection_test.db";
std::shared_ptr<RdbStore> RdbMultiThreadConnectionTest::store = nullptr;
std::string const RdbMultiThreadConnectionTest::CREATE_TABLE_SQL = "CREATE TABLE test (id INTEGER PRIMARY KEY "
                                                                   "AUTOINCREMENT, data1 TEXT,data2 INTEGER, data3 "
                                                                   "FLOAT, data4 BLOB, data5 ASSET, data6 ASSETS);";

class SqliteSharedOpenCallback : public RdbOpenCallback {
public:
    int OnCreate(RdbStore &rdbStore) override;
    int OnUpgrade(RdbStore &rdbStore, int oldVersion, int newVersion) override;
};

int SqliteSharedOpenCallback::OnCreate(RdbStore &rdbStore)
{
    return E_OK;
}

int SqliteSharedOpenCallback::OnUpgrade(RdbStore &rdbStore, int oldVersion, int newVersion)
{
    return E_OK;
}

void RdbMultiThreadConnectionTest::SetUpTestCase(void)
{
    RdbStoreConfig sqliteSharedRstConfig(RdbMultiThreadConnectionTest::DATABASE_NAME);
    SqliteSharedOpenCallback sqliteSharedRstHelper;
    int errCode = E_OK;
    RdbMultiThreadConnectionTest::store =
        RdbHelper::GetRdbStore(sqliteSharedRstConfig, 1, sqliteSharedRstHelper, errCode);
    EXPECT_NE(RdbMultiThreadConnectionTest::store, nullptr);
}

void RdbMultiThreadConnectionTest::TearDownTestCase(void)
{
    RdbHelper::DeleteRdbStore(RdbMultiThreadConnectionTest::DATABASE_NAME);
}

void RdbMultiThreadConnectionTest::SetUp()
{
    EXPECT_EQ(store->ExecuteSql(CREATE_TABLE_SQL), E_OK);
    GenerateDefaultTable();
}

void RdbMultiThreadConnectionTest::TearDown()
{
    EXPECT_EQ(store->ExecuteSql("DROP TABLE test"), E_OK);
}

void RdbMultiThreadConnectionTest::GenerateDefaultTable()
{
    std::shared_ptr<RdbStore> &store = RdbMultiThreadConnectionTest::store;

    int64_t id;
    ValuesBucket values;

    values.PutInt("id", 1);
    values.PutString("data1", std::string("hello"));
    values.PutInt("data2", 10);
    values.PutDouble("data3", 1.0);
    values.PutBlob("data4", std::vector<uint8_t> { 66 });
    store->Insert(id, "test", values);

    values.Clear();
    values.PutInt("id", 2);
    values.PutString("data1", std::string("2"));
    values.PutInt("data2", -5);
    values.PutDouble("data3", 2.5);
    values.PutBlob("data4", std::vector<uint8_t> {});
    store->Insert(id, "test", values);

    values.Clear();
    values.PutInt("id", 3);
    values.PutString("data1", std::string("hello world"));
    values.PutInt("data2", 3);
    values.PutDouble("data3", 1.8);
    values.PutBlob("data4", std::vector<uint8_t> {});
    store->Insert(id, "test", values);
}

void RdbMultiThreadConnectionTest::CreateTable()
{
    std::shared_ptr<RdbStore> &store = RdbMultiThreadConnectionTest::store;
    std::string CREATE_TABLE = "CREATE TABLE test";
    std::string CREATE_TABLE_COLUMN = " (id INTEGER PRIMARY KEY AUTOINCREMENT, "
        "data1 TEXT,data2 INTEGER, data3 FLOAT, data4 BLOB, data5 ASSET, data6 ASSETS);";
    for (uint32_t i = 0; i < 100; i++) {
        auto errCode = store->ExecuteSql(CREATE_TABLE + std::to_string(i) + CREATE_TABLE_COLUMN);
        EXPECT_EQ(E_OK, errCode);
    }
}

void RdbMultiThreadConnectionTest::Query()
{
    std::shared_ptr<RdbStore> &store = RdbMultiThreadConnectionTest::store;
    for (uint32_t i = 0; i < 100; i++) {
        auto resultSet = store->QuerySql("SELECT * FROM test");
        int rowCount = -1;
        resultSet->GetRowCount(rowCount);
        // 3 rows data in the test
        EXPECT_EQ(3, rowCount);
        resultSet->Close();
    }
}

HWMTEST_F(RdbMultiThreadConnectionTest, MultiThread_Connection_0001, TestSize.Level2, 10)
{
    CreateTable();
    Query();
}

/* *
 * @tc.name: Sqlite_Shared_Result_Set_Asset_Timeout
 * @tc.desc: normal testcase of SqliteSharedResultSet for move
 * @tc.type: FUNC
 * @tc.require: AR000134UL
 */
HWTEST_F(RdbMultiThreadConnectionTest, Sqlite_Shared_Result_Set_Asset_Timeout, TestSize.Level1)
{
    GenerateTimeoutTable();
    std::vector<std::string> selectionArgs;
    std::shared_ptr<ResultSet> rstSet =
        RdbMultiThreadConnectionTest::store->QuerySql("SELECT * FROM test", selectionArgs);
    EXPECT_NE(rstSet, nullptr);

    int ret = rstSet->GoToRow(0);
    EXPECT_EQ(ret, E_OK);

    int rowCnt = -1;
    ret = rstSet->GetRowCount(rowCnt);
    EXPECT_EQ(rowCnt, 1);

    Asset asset;
    rstSet->GetAsset(5, asset);
    EXPECT_EQ(asset.version, 1);
    EXPECT_EQ(asset.name, "name1");
    EXPECT_EQ(asset.uri, "uri1");
    EXPECT_EQ(asset.status, Asset::STATUS_INSERT);
}
